﻿using System;
using System.IO;
using System.Threading;
using EmbeddedSharp.Core;
using EmbeddedSharp.Core.Function;
using EmbeddedSharp.Scripting.Function;
using System.Collections.Generic;

namespace EmbeddedSharp.Scripting {
    public class ScriptingObject : IScriptingObject, IDebuggable, IDisposable {

        public string FileName {
            get;
            protected set;
        }

        public IContext Context {
            get;
            protected set;
        }

        public DebuggingState State {
            get;
            protected set;
        }

        public string Source {
            get;
            protected set;
        }

        protected int LineNumber = 0;

        /// <summary>
        /// Constructor with context
        /// </summary>
        /// <param name="context"></param>
        public ScriptingObject(IContext context) {
            this.Context = context;
            this.LineNumber = 0;
            this.State = DebuggingState.Idle;

            this.BreakPoints = new List<BreakPoint>();
        }


        /// <summary>
        /// Constructor with default context type
        /// </summary>
        public ScriptingObject()
            : this(new ScriptingContext()) {
        }

        /// <summary>
        /// Constructor with source and default context type
        /// </summary>
        /// <param name="source">script source</param>
        public ScriptingObject(string source)
            : this() {
            this.Source = source;
        }

        /// <summary>
        /// Constructor with specific context and source
        /// </summary>
        /// <param name="source">the source</param>
        /// <param name="cntx">the context</param>
        public ScriptingObject(string source, IContext cntx)
            : this(cntx) {
            this.Source = source;
        }

        public string[] Parameters {
            get;
            set;
        }

        public void Run() {
            IsDebugging = false;
            this.Start(Source, args);
        }

        protected Thread workerThread = null;

        public void RunAsync() {
            if (State == DebuggingState.Running) {
                return;
            }

            State = DebuggingState.Running;
            workerThread = new Thread(new ThreadStart(Run));
            workerThread.Start();
        }

        public void Pause() {
        }

        public void Abort() {
            try {
                if (workerThread == null) return;
                workerThread.Abort();
                workerThread.Join(50);
            } catch { }
            workerThread = null;
        }

        public bool IsDebugging {
            get;
            set;
        }

        public List<BreakPoint> BreakPoints {
            get;
            set;
        }

        /// <summary>
        /// check if the current line is a breakpoint
        /// </summary>
        /// <param name="lineNumber">the current line number</param>
        /// <returns></returns>
        public bool IsBreakPoint(int lineNumber) {
            foreach (BreakPoint bp in BreakPoints) {
                if (bp.LineNumber == lineNumber) {
                    return true;
                }
            }

            return false;
        }

        private string[] stmts, args;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="args"></param>
        public void Start(string source, string[] args) {
            stmts = source.Split(';');
            this.args = args;
            LineNumber = 0;

            Resume();
        }

        /// <summary>
        /// 
        /// </summary>
        public void Resume() {
            try {
                for (int index = LineNumber; index < stmts.Length; index++) {
                    string stmt = stmts[index];
                    LineNumber++;
                    if (IsDebugging && IsBreakPoint(LineNumber)) {
                        DebuggingEventArgs e = new DebuggingEventArgs();
                        e.LineNumber = LineNumber;
                        OnBreak(e);
                        return;
                    }

                    this.Process(stmt);
                }

                FuncNode mainEntry
                    = this.Context.GetFunctionManager().GetFunction("Main");
                if (mainEntry != null) {
                    string expr = ShellUtils.InitUDF(
                        mainEntry as UserDefinedFunctionNode, args);

                    object result = Context.Evaluate(expr); // DefaultDependencyManager.Instance.ParserFactory.Evaluate(expr);
                    Console.WriteLine(result.ToString());
                }
            } catch (TerminateException te) {
                Console.WriteLine("Exit: " + te.Message);
                DebuggingEventArgs e = new DebuggingEventArgs();
                e.LineNumber = LineNumber;
                OnStop(e);
            }

            DebuggingEventArgs de = new DebuggingEventArgs();
            de.LineNumber = LineNumber;
            OnStop(de);
        }

        public void Stop() {
            DebuggingEventArgs de = new DebuggingEventArgs();
            de.LineNumber = LineNumber;
            OnStop(de);
        }

        public event EventHandler<DebuggingEventArgs> Started;

        public event EventHandler<DebuggingEventArgs> Breaked;

        public event EventHandler<DebuggingEventArgs> Resumed;

        public event EventHandler<DebuggingEventArgs> Restarted;

        public event EventHandler<DebuggingEventArgs> Stopped;

        protected void Activate(
            EventHandler<DebuggingEventArgs> e, DebuggingEventArgs args) {
            EventHandler<DebuggingEventArgs> temp = e;

            if (temp != null) {
                temp(this, args);
            }
        }

        protected virtual void OnStart(DebuggingEventArgs args) {
            this.State = DebuggingState.Running;
            Activate(Started, args);
        }

        protected virtual void OnBreak(DebuggingEventArgs args) {
            this.State = DebuggingState.Breaked;
            Activate(Breaked, args);
        }

        protected virtual void OnResume(DebuggingEventArgs args) {
            this.State = DebuggingState.Running;
            Activate(Resumed, args);
        }

        protected virtual void OnRestart(DebuggingEventArgs args) {
            this.State = DebuggingState.Running;
            Activate(Restarted, args);
        }

        protected virtual void OnStop(DebuggingEventArgs args) {
            this.State = DebuggingState.Stopped;
            Activate(Stopped, args);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public object Process(string input) {
            if (string.IsNullOrEmpty(input)) {
                return string.Empty;
            }

            //((Context)this.Context).InputHistory.Add(input);
            object output = null;

            //try {
            Utils.Debugging("input: " + input);
            output = this.ProcessInternal(input);
            //((Context)this.Context).OutputHistory.Add(output);
            //catch (Exception ex) {
            //    //((Context)this.Context).ErrorHistory.Add(ex);
            //    Utils.Log(ex.Message + "\n" + ex.StackTrace);
            //    throw ex;
            //}

            return output;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        protected virtual object ProcessInternal(string input) {
            string output = string.Empty;

            Lexer lexer = new Lexer(input);
            Token token = lexer.PeekToken(1);
            Token nt = lexer.PeekToken(2);
            if (token.TokenType == TokenType.IDENTIFIER
                && token.Value == "class") {
                UserDefinedType type = new UserDefinedType();

                type.Name = nt.Value;
                lexer.NextToken();
                lexer.NextToken();

                lexer.NextToken("{");
                while (
                    (token = lexer.NextToken()).TokenType
                    != TokenType.RightBracket) {
                    break;
                }
            }
            if (input.Contains(":=")) {
                // it is a function def
                return AddFunction(input);
            }

            return this.Context.Evaluate(input);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public string AddFunction(string input) {
            UserDefinedFunctionParser udfp = new UserDefinedFunctionParser(Context.GetParserFactory());
            UserDefinedFunctionNode udf = udfp.Parse(input);

            this.Context.GetFunctionManager().Add(udf.Name, udf);
            return string.Format("Function {0} was created.", udf.Name);
        }


        #region IDisposable Members

        public void Dispose() {
            this.Context.Dispose();
        }

        #endregion

        #region IDebugger Members

        public void StartDebugging() {
            IsDebugging = true;

            DebuggingEventArgs e = new DebuggingEventArgs();
            e.LineNumber = 0;
            OnStart(e);

            this.Start(Source, args);
        }

        public void Restart() {
            throw new NotImplementedException();
        }

        public void StepInto() {
            throw new NotImplementedException();
        }

        public void StepOver() {
            throw new NotImplementedException();
        }

        #endregion

        #region IScriptingObject Members

        /// <summary>
        /// Evaluate an expression
        /// </summary>
        /// <param name="expr">the expression</param>
        /// <returns></returns>
        public object Evaluate(string expr) {
            return Context.Evaluate(expr);
        }

        #endregion
    }
}
